<?php
/**
 * @author 3Code Group 
 * @author Pawel Giemza
 * @version 1.3
 * @package Arrow
 * @subpackage Model
 * @link http://arrowplatform.org/
 * @copyright 2009 3code 
 * @license GNU LGPL
 * 
 * @date 2009-06-01 
*/

ArrowController::importModel("org.arrowplatform.model.ArrowModelIterator");
ArrowController::importModel("org.arrowplatform.model.IArrowModel");
/**
 * Base for all data objects used in Arrow framework. 
 * 
 * This class delivers basic functionality required to access model configuration and store data.
 * 
 * ArrowModel uses one configuration option (setting):
 * - return_only_existing_values - if this option is set to true ArrowModel will throw ArrowException
 * when trying to access field which has not been set (has no value). 
 * This setting is assumed true by default
 *
 */
class ArrowModel implements ArrayAccess, IteratorAggregate, IArrowModel{
	/**
	 * 
	 * @var Array - names and values of configuration variables
	 */
	protected static $settings = array();
	/**
	 * 
	 * @var Array - storing information whether initialization for given class has been performed already
	 */
	protected static $init = array();
	/**
	 * 
	 * @var Array - fields stored in this object
	 */
	protected $data = array();
	/**
	 * 
	 * @var String - object class
	 */
	protected $class = '';
	/**
	 * Basic constructor. Loads settings
	 */
	protected function __construct() {
		$this->class = get_class($this);
		$class = $this->class;
		//ArrowLogger::log("[ArrowModel] __construct() class=>$class", ArrowLogger::EL_INFO );
		if (!isset(self::$init[$class]) || self::$init[$class]==false ){
			$this->_init();
			if (!self::$init[$class])
				throw new ArrowException('parent::_init not called in _init function for class '.$class,1);
		}		 		
	}

	/**
	 * Ensure that model is correctly initialized when unserialized
	 * @return unknown_type
	 */
	public function __wakeup() {
		$this->class = get_class($this);
		$class = $this->class;
		//ArrowLogger::log("[ArrowModel] __wakeup() class=>$class", ArrowLogger::EL_INFO );
		if (!isset(self::$init[$class]) || self::$init[$class]==false ){
			$this->_init();
			if (!self::$init[$class])
				throw new ArrowException('parent::_init not called in _init function for class '.$class,1);
		}		 		
	}	

	/**
	 * This function should be used to initialize class.
	 * It is called only once when first object of given class is being instanciated.
	 * @note Remember to call parent::init(); if using this function in your model. 
	 */
	protected function _init(){
		$class = $this->class;
		//ArrowLogger::log("[ArrowModel] _init() class=>$class", ArrowLogger::EL_INFO );
		//$settings = ArrowSettings::getDefault();
		//$settings = $settings->getSetting($class,'setting');
		//self::$settings[$class] = $settings;
		self::$settings[$class] = true;
		self::$init[$class] = true;
	}
	
	/**
	 * Tests whether class initialization has been completed.
	 * @note required by plugins
	 * @return bool
	 */
	final public function afterInit(){
		//ArrowLogger::log("[ArrowModel] __construct() class =>".get_class($this), ArrowLogger::EL_INFO );
		if ( isset( self::$init[$this->class]) )
			return true;
		return false;	
	}
	
	/**
	 * Returns value of setting (from class or stored in Arrow variable)
	 * 
	 * @param $setting_name - name of setting to retrieve
	 * @return string - value of setting
	 */
	final protected function getSetting($setting_name){	

		$class = $this->class;
		//ArrowLogger::log("[ArrowModel] getSetting() class=>$class", ArrowLogger::EL_INFO );
		if (!isset(self::$settings[$class]))
			throw new ArrowException(array('msg'=>'[ArrowModel] Settings module not initialised', 'class'=>get_class($this),'hint'=>'Check if there is parent::_init in your _init function'));
		
		//$settings = self::$settings[$class];
		$setting = null;//$settings->getSetting($setting_name);

		if ($setting === null){
			$setting = ArrowSettings::getDefault();
			$setting = $setting->getSetting('arrow.'.$setting_name);
		}
		return $setting;	
	}

	/**
	 * Returns value of field stored in instance of ArrowModel object or null if this field is not set.
	 * This function will throw an ArrowException if field is not set and 'return_only_existing_values' is set to true. This setting is assumed true by default.
	 * 
	 * @param String $field - Field name
	 * @return Mixed
	 */
	public function getValue($field){
		////ArrowLogger::log("[ArrowModel] getValue() class=>".get_class($this)." field=>$field", ArrowLogger::EL_INFO );
		$retVal = null;
		//$exists = $this->valueExists($field);
		//if (!$exists)
		//	throw new ArrowException(array("msg"=>"[ArrowModel] Trying to return property which does not exist", "field"=>$field) );
		//array_key_exist must be used because isset returns false for variables that are set to null.
		if (array_key_exists($field,$this->data) )
			$retVal = $this->data[$field];
		else
			throw new ArrowException(array("msg"=>"[ArrowModel] Trying to return property which does not exist", "field"=>$field, "class"=>$this->class) );
		return $retVal;
	}

	/**
	 * Basic value setter.
	 * @param String $field - Field name
	 * @param Mixed $value - Field value
	 */
	public function setValue($field, $value, $tmp = false ){	
		//ArrowLogger::log("[ArrowModel] setValue()','class'=>".get_class($this).", 'field'=>$field, 'value'=>$value", ArrowLogger::EL_INFO );
		$this->data[$field] = $value;
	}

	/**
  	 * Unsets (removes) field from model
  	 * @param  String $field - Field name
  	 */
	public function unsetValue($field){
		//ArrowLogger::log("[ArrowModel] unsetValue()','class'=>".get_class($this).", 'field'=>$field", ArrowLogger::EL_INFO );
		if ( array_key_exists($field,$this->data) )
			unset($this->data[$field]);
	}

	/**
 	 * Tests whether given field is set in this model
 	 * @param String $field - Field name 
 	 * @return bool
 	 */
	public function valueExists($field){
		//ArrowLogger::log("[ArrowModel] valueExists()','class'=>".get_class($this).", 'field'=>$field", ArrowLogger::EL_INFO );
		//array_key_exist must be used because isset returns false for variables that are set to null.
		if ( array_key_exists($field,$this->data) )
			return true;		
	    return false; 
	}	
	
	//--------------------implementation of IteratorAggregate interface---------------
	/**
	 * Return instance of iterator used by ArrowModel.
	 * TODO: Integration with data fields access rights
	 * @return Object
	 */
	public function getIterator(){
		$fieldNames = array();
		foreach ($this->data as $key=>$value)
			$fieldNames[] = $key;
		return new ArrowModelIterator($this, $fieldNames);
	}	
	
	//--------------------implementation of ArrayAccess interface--------------------- 	
	/**
	 * Alias to getValue function. Part of ArrayAccess interface.
 	 *
 	 * @param String $offset
	 * @return Mixed $value
	 */		
	final public function offsetGet($offset){
		//ArrowLogger::log("[ArrowModel] offsetGet','class'=>".get_class($this).", 'offset'=>$offset", ArrowLogger::EL_INFO );
		return $this->getValue($offset);
	}

	/**
	 * Alias to setValue function. Part of ArrayAccess interface.
 	 *
 	 * @param String $offset
	 * @param Mixed $value  	 
	 * @return void
	 */		
	final public function offsetSet($offset,$value){
		//ArrowLogger::log("[ArrowModel] offsetSet','class'=>".get_class($this).", 'offset'=>$offset, 'value'=>$value", ArrowLogger::EL_INFO );
		$this->setValue($offset,$value);	
	}

	/**
	 * Alias to valueExists function. Part of ArrayAccess interface.
 	 *
 	 * @param String $offset 	 
	 * @return bool
	 */		
	final public function offsetExists($offset){		
		//ArrowLogger::log("[ArrowModel] offsetExists','class'=>".get_class($this).", 'offset'=>$offset", ArrowLogger::EL_INFO );
		return $this->valueExists($offset);
	}

	/**
	 * Alias to unsetValue function. Part of ArrayAccess interface.
	 *
 	 * @param String $offset	 
	 * @return void
	 */		
	final public function offsetUnset($offset){
		//ArrowLogger::log("[ArrowModel] offsetUnset','class'=>".get_class($this).", 'offset'=>$offset", ArrowLogger::EL_INFO );
		$this->unsetValue($offset);
	}   
	
	/**
	 * Returns model classpath
	 * @return string
	 */
	public function getModel(){
		throw new ArrowCoreException(array("msg" => "ArrowModel::getModel not implemented in this class"));
	}
	
	/**
	 * Returns object class
	 * @return  string
	 */
	public function getClass() {
		return $this->class ;
	}
	/**
	 * REturns class by given classpath
	 * @param string model classpath
	 * @return string
	 */
	public static function getModelClass( $model ){
    $tmp = explode(".",$model);
    return array_pop($tmp);
	}
}
?>